package com.guo.anim.fallingstar;


import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 通过重写ImageView实现流星环绕效果
 * 为了避免频繁创建对象引起大量的内存抖动问题，计划一次创建全部的流星对象，动态设置其显示
 */
public class FallingStarView extends AppCompatImageView {
    private static final String TAG = "FallingStarView";
    private Paint fallingLinePaint , starPaint;
    //视图的宽度和高度
    private int width , height;
    //中心圆形图片的半径，规则为直径为最小边长的1/2
    private int mInsideImageRadius;
    //流星圆弧的最大外径
    private int mOutSideStarRadius;
    //流星轨迹的最大阈值角度
    private static final int MAX_SWEEP_ANGLE = 60;
    //一组流星雨的个数
    private int FALLING_STAR_GROUP_SIZE = 50;
    //流星雨最大轨道数
    private int PATH_COUNT_MAX = 3;

    private final static int VIEW_TIMES_EACH_PATH = 2;
    private List<FallingStar> mFallingStarList = new ArrayList<>(FALLING_STAR_GROUP_SIZE);
    //流星运转速度的随机数，范围为1~5
    private Random mRandomInt = new Random();
    //流星雨轨道层的像素间距
    private final static int PATH_INTERVAL_FALLING_STAR = 10;
    public FallingStarView(Context context) {
        super(context);
        initPaint();
    }

    public FallingStarView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public FallingStarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        width = getMeasuredWidth();
        height = getMeasuredHeight();
        //计算流星最外轨道半径
        mOutSideStarRadius = Math.min(width, height) / 2 * 9 / 10;
        //计算中心原图的半径
        mInsideImageRadius = mOutSideStarRadius * 4 / 5;
        //计算可设置的流星雨最大个数
        PATH_COUNT_MAX = (mOutSideStarRadius - mInsideImageRadius) / PATH_INTERVAL_FALLING_STAR + 1;
//        FALLING_STAR_GROUP_SIZE = VIEW_TIMES_EACH_PATH * PATH_COUNT_MAX * 360 / MAX_SWEEP_ANGLE;
        //初始化流星群组
        initFallingStarAngle();
    }

    //中心图片的旋转Matrix
    private Matrix mMatrix = new Matrix();
    //中心图片的旋转角度
    private int mRotateAngle = 0;
    //处理后的圆形图片
    private Bitmap roundBitmap;
    //旋转后的图片
    private Bitmap rotateBitmap;

    @Override
    public void setImageBitmap(Bitmap bm) {
        if(null != roundBitmap && !roundBitmap.isRecycled()){
            roundBitmap.recycle();
            roundBitmap = null;
        }
        if(null != rotateBitmap && !rotateBitmap.isRecycled()){
            rotateBitmap.recycle();
            rotateBitmap = null;
        }
        super.setImageBitmap(bm);
    }

    @Override
    public void setImageResource(int resId) {
        if(null != roundBitmap && !roundBitmap.isRecycled()){
            roundBitmap.recycle();
            roundBitmap = null;
        }
        if(null != rotateBitmap && !rotateBitmap.isRecycled()){
            rotateBitmap.recycle();
            rotateBitmap = null;
        }
        super.setImageResource(resId);
    }

    @Override
    public void setImageDrawable(@Nullable Drawable drawable) {
        if(null != roundBitmap && !roundBitmap.isRecycled()){
            roundBitmap.recycle();
            roundBitmap = null;
        }
        if(null != rotateBitmap && !rotateBitmap.isRecycled()){
            rotateBitmap.recycle();
            rotateBitmap = null;
        }
        super.setImageDrawable(drawable);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //设置画布透明
        canvas.drawARGB(0,0,0,0);
        if(null == roundBitmap){
            //绘制中间的圆形图片
            Drawable drawable = getDrawable();
            if(null == drawable){
                return;
            }
            //将ImageView的原图裁剪成圆形图片
            Bitmap bitmap = ((BitmapDrawable)drawable).getBitmap();
            roundBitmap = RoundBitmapUtil.createRoundBitmap(bitmap, mInsideImageRadius);
        }
        if(null == rotateBitmap){
            //通过Matrix设置圆形Bitmap旋转
            mMatrix.reset();
            mMatrix.setRotate(mRotateAngle);
            //获取旋转后的Bitmap
            rotateBitmap = Bitmap.createBitmap(roundBitmap, 0, 0, 2*mInsideImageRadius, 2*mInsideImageRadius, mMatrix, false);
        }

        //在画布上绘制旋转后的Bitmap，注意基于Matrix旋转后的Bitmap与原图的大小并不相等，故计算中心位置时应以转换后的Bitmap进行计算
        canvas.drawBitmap(rotateBitmap, width / 2 - rotateBitmap.getWidth()/2 , height / 2 - rotateBitmap.getHeight()/2, null);
        //绘制流星
        drawFallingStarGroup(canvas);
        if(null != rotateBitmap && !rotateBitmap.isRecycled()){
            rotateBitmap.recycle();
        }
        rotateBitmap = null;
        //33ms后更新流星位置
        postDelayed(new Runnable() {
            @Override
            public void run() {
                changeFallingStarAngle();
            }
        }, 33);
    }



    /**
     * 初始化画笔
     */
    private void initPaint(){
        fallingLinePaint = new Paint();
        fallingLinePaint.setStrokeWidth(1);
        fallingLinePaint.setStyle(Paint.Style.STROKE);
        fallingLinePaint.setColor(0xFFE0E0E0);
        fallingLinePaint.setAntiAlias(true);
        starPaint = new Paint();
        starPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        starPaint.setColor(0xFFE0E0E0);
        starPaint.setAntiAlias(true);
        setBackgroundColor(0x00E0E0E0);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mFallingStarList.clear();
    }

    /**
     * 初始化流星群组的角度和半径参数
     */
    private void initFallingStarAngle(){
        mFallingStarList.clear();
        int starPathNo = 1;
        int beginRandomAngle = mRandomInt.nextInt(360);
        for(int i = 0; i < FALLING_STAR_GROUP_SIZE; i++){
            starPathNo = i / (VIEW_TIMES_EACH_PATH * 360 / MAX_SWEEP_ANGLE) + 1;
            FallingStar star = new FallingStar()
                    //设置流星运动半径
                    .setRadius(mInsideImageRadius + starPathNo * PATH_INTERVAL_FALLING_STAR)
                    //设置起点角度
                    .setStartAngle(i % (360 / MAX_SWEEP_ANGLE) * MAX_SWEEP_ANGLE + (starPathNo - 1) * 360 / PATH_COUNT_MAX + beginRandomAngle)
                    //设置扫过角度
                    .setSweepAngle(mRandomInt.nextInt(MAX_SWEEP_ANGLE/6) + 5)
                    .build();
            mFallingStarList.add(star);
        }
    }

    /**
     * 绘制流星群组
     */
    private void drawFallingStarGroup(Canvas canvas){
        //剩余流星不及原来总数50%时补充流星
        if(mFallingStarList.size() <= 0){
            initFallingStarAngle();
        }
        //绘制流星群组，休眠期不绘制
        for(int i = 0; i < mFallingStarList.size(); i++){
            if(mFallingStarList.get(i).mStatus != SLEEP){
                drawFallingStar(canvas, mFallingStarList.get(i));
            }
        }
    }

    /**
     * 调整流星群组的起点角度和扫过角度，起点角度的算法是逆时针匀速调整，扫过角度的算法是阈值及以上匀速减少至0，阈值以下匀速增加至阈值
     */
    private void changeFallingStarAngle(){
        for(int i = 0; i < mFallingStarList.size(); i++){
            mFallingStarList.get(i).changeAngle();
        }
        //调整中心图片的旋转角度，设置进行逆时针旋转
        mRotateAngle += 2;
        mRotateAngle = (mRotateAngle + 360) % 360;
        invalidate();
    }

    /**
     * 绘制单个流星包括绘制星星头部的圆形和绘制流星轨迹的渐变线
     * @param canvas
     * @param fallingStar
     */
    private void drawFallingStar(Canvas canvas, FallingStar fallingStar){
        //绘制流星头部圆形
        canvas.drawCircle(fallingStar.getStarCenterX(), fallingStar.getStarCenterY(), 2 , starPaint);
        //绘制轨迹线圆弧，设置弧线为渐变色
        fallingLinePaint.setShader(fallingStar.getShader());
        canvas.drawArc(fallingStar.getRect(), fallingStar.getStartAngle(), fallingStar.getSweepAngle(), false, fallingLinePaint);
    }

    //流星规则的渐变色
    private final int[] SWEEP_COLORS = new int[]{0x40E0E0E0, 0xFFE0E0E0};
    private final float[] POSITIONS = new float[]{0.0f, 1.0f};
    //流星生命状态，RISE增长期；DECLINE衰减期; SLEEP 为休眠期
    public final static int RISE = 1;
    public final static int DECLINE = 2;
    public final static int SLEEP = 3;
    @IntDef({RISE, DECLINE, SLEEP})
    public @interface StarType{};
    /**
     * 流星对象,主要由起点角度/扫过角度/圆弧半径/流星头部的圆心横坐标/流星头部的纵坐标 5个要素构成
     * 每一个流星会经历 增长期 --> 衰减期 --> 休眠期，其中休眠期不绘制流星，但是流星的坐标仍然会继续调整
     */
    private class FallingStar{
        private int startAngle;
        private int sweepAngle;
        private int radius;
        //流星头部圆形的横纵坐标
        private int starCenterX;
        private int starCenterY;
        private SweepGradient shader;
        private RectF rect;
        //流星的运动状态
        private @StarType int mStatus;
        //流星的已休眠时长，在流星处于休眠状态时需要计数，其他状态设定为0
        private int mSleepTimer;
        //流星的总休眠时长，建议为20～40不等
        private int mSleepPeriod;

        //扫过的终止角度
        private double endAngle;

        //流星旋转速度
        private int velocity;
        //Shader方位旋转矩阵,确保渐变色跟随起点角度，实现美观的效果
        private Matrix matrix = new Matrix();


        public int getStartAngle() {
            return startAngle;
        }

        public FallingStar setStartAngle(int startAngle) {
            this.startAngle = startAngle;
            return this;
        }

        public int getSweepAngle() {
            return sweepAngle;
        }

        public FallingStar setSweepAngle(int sweepAngle) {
            this.sweepAngle = sweepAngle;
            return this;
        }

        public int getRadius() {
            return radius;
        }

        public FallingStar setRadius(int radius) {
            this.radius = radius;
            return this;
        }

        public int getStarCenterX() {
            return starCenterX;
        }

        public int getStarCenterY() {
            return starCenterY;
        }

        public SweepGradient getShader() {
            return shader;
        }

        public RectF getRect() {
            return rect;
        }

        public FallingStar build(){
            //计算流星头部的圆心坐标
            endAngle = 2 *Math.PI*(getStartAngle()+getSweepAngle()) / 360 ;
            starCenterX = (int)(radius*Math.cos(endAngle)) + width/2;
            starCenterY = (int)(radius*Math.sin(endAngle)) + height/2;
            shader = new SweepGradient(width/2, height/2, SWEEP_COLORS, POSITIONS);
            //初始化shader的旋转角度
            matrix.setRotate(startAngle, width/2, height/2);
            shader.setLocalMatrix(matrix);
            rect = new RectF(width/2 - getRadius(), height/2 - getRadius(), width/2 + getRadius(), height/2 + getRadius());
            //初始状态随机设置为增长期 1 或者休眠期 3
            mStatus = mRandomInt.nextInt(2);
            mSleepPeriod = mRandomInt.nextInt(21) + 20;
            mSleepTimer = 0;
            if(mStatus == 0){
                //休眠期
                mStatus = FallingStarView.SLEEP;
            }
            velocity = mRandomInt.nextInt(2) + 2;

            return this;
        }

        /**
         * 调整流星起点角度和扫过角度，起点角度的算法是逆时针匀速调整，扫过角度的算法是阈值及以上匀速减少至0，阈值以下匀速增加至阈值
         */
        public void changeAngle(){
            startAngle +=velocity;
            if(mStatus == RISE){
                //增长期的调整
                sweepAngle ++;
                if(sweepAngle > MAX_SWEEP_ANGLE){
                    //进入衰退期
                    mStatus = DECLINE;
                    sweepAngle = MAX_SWEEP_ANGLE;
                }
            }else if(mStatus == DECLINE){
                //衰减期的调整
                sweepAngle --;
                //为了确保调整速度，起点角保持增加
                startAngle ++;
                if(sweepAngle <= 0){
                    mStatus = SLEEP;
                    sweepAngle = 0;
                }
            }else{
                //休眠期的调整
                mSleepTimer ++;
                if(mSleepTimer >= mSleepPeriod){
                    //结束休眠进入增长期
                    mSleepTimer = 0;
                    mStatus = RISE;
                }
            }
            startAngle %= 360;
            //相应的调整圆头的位置
            endAngle = 2 *Math.PI*(getStartAngle()+getSweepAngle()) / 360 ;
            starCenterX = (int)(getRadius()*Math.cos(endAngle)) + width/2;
            starCenterY = (int)(getRadius()*Math.sin(endAngle)) + height/2;
            //调整shader旋转角度
            matrix.reset();
            matrix.setRotate(startAngle, width/2, height/2);
            shader.setLocalMatrix(matrix);
        }
    }
}
